iT邦幫忙

2023 iThome 鐵人賽

DAY 30
0
自我挑戰組

Unit Test 學習路系列 第 30

Day 29: Mocking HTTP Request (二)

  • 分享至 

  • xImage
  •  

今天來實作模擬 Call API 測試吧!

測試情境:我要 Call API 拿到回傳 User 資料,並顯示在畫面上。
前情提要:昨天已經完成 MSW 模擬 API 回傳設定:

// mocks/handler
export const handlers = [
    rest.get("https://jsonplaceholder.typicode.com/users", (req, res, ctx) => {
        return(
            res(
                ctx.status(200),
                ctx.json([
                    {
                        "id": 1,
                        "name": "Leanne Graham",
                        "username": "Bret",
                        "email": "Sincere@april.biz",
                        "address": {
                          "street": "Kulas Light",
                          "suite": "Apt. 556",
                          "city": "Gwenborough",
                          "zipcode": "92998-3874",
                          "geo": {
                            "lat": "-37.3159",
                            "lng": "81.1496"
                          }
                        },
                        "phone": "1-770-736-8031 x56442",
                        "website": "hildegard.org",
                        "company": {
                          "name": "Romaguera-Crona",
                          "catchPhrase": "Multi-layered client-server neural-net",
                          "bs": "harness real-time e-markets"
                        }
                    },
                ])
            )
        )
    })
];

測試 API Response Success

撰寫元件 Users,畫面上顯示 username,資料來源為 API 回傳資料

import { useEffect, useState } from "react"
import UserType from "./user.type";

export default function Users(){
    const [users, setUsers] = useState<UserType[]>([]);
    const [error, setError] = useState<string|null>(null);


    useEffect(() => {
        fetch("https://jsonplaceholder.typicode.com/users")
        .then(res => res.json())
        .then(data => {
            setUsers(data);
            setError(null);
        })
        .catch(err => {
            setUsers([]);
            setError("No Data");
        })
    }, [])

    return(
        <>
            <h1>User List</h1>
            {error && <h3>{error}</h3>}
            {users.length > 0
                &&
                <ul>
                    {   users.map(item => (
                        <li key={item.id}>{item.username}</li>
                    ))}
                </ul>
            }
        </>
    )
}

撰寫測試:取得 listitem 內容長度是否符合 MSW 模擬回傳長度(目前有一筆資料)。

describe("User", () => {
    test("Render mutiple listItemes after calling API.", async () => {
        render(<Users />);

        const userItemElements = await screen.findAllByRole("listitem");
        expect(userItemElements).toHaveLength(1);
    })
})

測試結果:
FAIL src/components/users/user.test.tsx
● User › Render mutiple listItemes after calling API.
expect(received).toHaveLength(expected)
Expected length: 1
Received length: 10

顯示錯誤原因:因為執行測試沒有使用 MSW,而是實際 Call API,預設會回傳 10 users 資料。

新增程式碼:加入MSW 官網:setupTests才能實際將 MSW 加入測試模擬。

// src/setupTests.ts
import { server } from './mocks/server.js'
// Establish API mocking before all tests.
beforeAll(() => server.listen())

// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers())

// Clean up after the tests are finished.
afterAll(() => server.close())

** 補充說明:**
beforeAll afterEach afterAll 是由 Jest 提供的方法。
三個函式都會有兩個參數:執行函式、函式等待時間(單位:毫秒,預設5秒,選填)
beforeAll afterEach afterAll三個函式執行時間軸

測試結果:
PASS src/components/users/user.test.tsx


測試 API Response Error

剛剛測試API回傳成功是否符合預期時,我們多加了 setupTests.ts 讓每一次測試都會經過 MSW。
但是我們可以想想:
如果我現在要測試 API回傳失敗是否符合預期時,我直接 handler.ts 內的 cts.status(500),合適嗎?

  • 第一:
    我的測試群組內,可能有多個測試API回傳成功的函式,直接改 statusCode 顯然會讓我其他測試內容報錯。
  • 第二:
    測試API回傳失敗的測試相對來說,回傳的錯誤訊息相對固定,是否可以只針對這個測試撰寫指定的 handler.ts 即可呢?
  • 第三:
    如果我針對特定情況撰寫的 handler,怎麼覆蓋原本的設定?(setupTests.ts)

來試試第二點與第三點怎麼實作吧!

撰寫測試:

import { render, screen } from "@testing-library/react";
import Users from "./users";
import { rest } from "msw";
import { server } from "../../mocks/server";

describe("User", () => {
    test("Render Error Message after calling API.", async () => {
        server.use(
            // 建立單獨模擬 call API fail 的 handler
            rest.get("https://jsonplaceholder.typicode.com/users", (_, res, ctx) => (
                res(ctx.status(500))
            ))
        )

        render(<Users />);
        const errorMsgEl = await screen.findByRole("heading", {
            level: 3
        });
        expect(errorMsgEl).toHaveTextContent("No Data");
    })
})

補充說明:

  • 使用 模擬server.use()的 middleware,讓特定 handler 只作用在這個測試中,同時屏蔽原廠 setupServer 設定。
  • 在指定測試項目中,建立單獨模擬 call API fail 的 handler,不會影響其他測試項目。

測試結果:
PASS src/components/users/user.test.tsx

故意寫錯測試錯誤訊息,來驗證一下測試有沒有成功:

測試結果:
FAIL src/components/users/user.test.tsx
● User › Render Error Message after calling API.

expect(element).toHaveTextContent()

Expected element to have text content:
  No Data!
Received:
  No Data

參考資源


上一篇
Day 28: Mocking HTTP Request (一)
下一篇
Day 30: 延伸到 Side Project 的自我挑戰
系列文
Unit Test 學習路31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言